package de.jmda.sample.mproc.task;

import static de.jmda.core.mproc.ProcessingUtilities.getElementsAnnotatedWith;
import static de.jmda.core.util.StringUtil.sb;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;

import javax.lang.model.element.Element;

import org.junit.Before;
import org.junit.Test;

import de.jmda.core.mproc.task.AbstractAnnotatedElementsTask;
import de.jmda.core.mproc.task.TaskException;
import de.jmda.core.mproc.task.TaskRunner;
import de.jmda.core.util.FileUtil;
import de.jmda.core.util.fileset.FileSet;
import de.jmda.core.util.fileset.FileSet.RuleSetUpdateMode;

@interface Annotation1 {}
@interface Annotation2 {}

public class JUTAnnotationsTaskClaimingAnnotations
{
	@Annotation1
	private Object annotatedElement1;
	@Annotation2
	private Object annotatedElement2;

	/** the files that will be processed by {@link AnnotationsTaskClaimer} */
	private FileSet fileSet;
	
	/**
	 * annotations task that will ({@link #claim} == true) or will not ({@link
	 * #claim} == false) claim the annotations that it processes
	 */
	private class AnnotationsTaskClaimer extends AbstractAnnotatedElementsTask
	{
		private boolean claim;
		private Set<Element> annotatedElements = new HashSet<>();

		public AnnotationsTaskClaimer(
		    Set<Class<? extends Annotation>> annotationTypes,
		    FileSet fileset,
		    boolean claim)
		{
			super(annotationTypes, fileset);
			this.claim = claim;
		}

		@Override
		public boolean execute() throws TaskException
		{
			for (Class<? extends Annotation> annotationType : getAnnotationTypes())
			{
				annotatedElements.addAll(getElementsAnnotatedWith(annotationType));
			}

			return claim;
		}
	}

	/**
	 * careful setup for tests to be run
	 *
	 * @throws IOException
	 */
	@Before
	public void before() throws IOException
	{
		fileSet = new FileSet();
		fileSet.includeFile(
				new File(
						"src/test/java",
						FileUtil.toSourceFilePath(getClass())), RuleSetUpdateMode.ADD_RULE);

		Set<File> calculatedFileSet = fileSet.calculateFileSet();

		if (calculatedFileSet.size() != 1)
		{
			fail("unexpected number of calculated files: " + calculatedFileSet.size());
		}

		File calculatedFile = calculatedFileSet.iterator().next();

		if (false == calculatedFile.exists())
		{
			fail("calculated file does not exist: " + calculatedFile.getCanonicalPath());
		}
	}

	/**
	 * Two tasks will process an identical set of annotations where the first task
	 * will not claim the annotations. Because the first task does not claim the
	 * annotations the second task should "see" all annotated elements as well.
	 *
	 * @throws IOException
	 */
	@Test
	public void claimFalseIdenticalAnnotationSet() throws IOException
	{
		Set<Class<? extends Annotation>> annotationTypes = new HashSet<>();

		annotationTypes.add(Annotation1.class);
		annotationTypes.add(Annotation2.class);

		AnnotationsTaskClaimer task1 =
				new AnnotationsTaskClaimer(annotationTypes, fileSet, false);
		AnnotationsTaskClaimer task2 =
				new AnnotationsTaskClaimer(annotationTypes, fileSet, false);

		TaskRunner.run(task1, task2);

		assertEquals(
				"unexpected number of annotated elements after non claiming task",
				2,
				task2.annotatedElements.size());

		printResult("case 1 (same annotation sets, claim false)\n", task1, task2);
	}

	/**
	 * Two tasks will process an identical set of annotations where the first task
	 * will claim the annotations. Because the first task claims the annotations
	 * the second task should not "see" annotated elements.
	 *
	 * @throws IOException
	 */
	@Test
	public void claimTrueIdenticalAnnotationSet() throws IOException
	{
		Set<Class<? extends Annotation>> annotationTypes = new HashSet<>();

		annotationTypes.add(Annotation1.class);
		annotationTypes.add(Annotation2.class);

		AnnotationsTaskClaimer task1 =
				new AnnotationsTaskClaimer(annotationTypes, fileSet, true);
		AnnotationsTaskClaimer task2 =
				new AnnotationsTaskClaimer(annotationTypes, fileSet, false);

		TaskRunner.run(task1, task2);

		assertEquals(
				"unexpected number of annotated elements after claiming task",
				0,
				task2.annotatedElements.size());

		printResult("case 2 (same annotation sets, claim true)\n", task1, task2);
	}

	/**
	 * Two tasks will be run. The processed set of annotations of the first task
	 * is a super set of the annotations being processed by the second one. The
	 * first task will claim the annotations so the second task should not "see"
	 * annotated elements.
	 *
	 * @throws IOException
	 */
	@Test
	public void claimTrueSuperAnnotationSet() throws IOException
	{
		Set<Class<? extends Annotation>> annotationTypes1 = new HashSet<>();

		annotationTypes1.add(Annotation1.class);
		annotationTypes1.add(Annotation2.class);

		Set<Class<? extends Annotation>> annotationTypes2 = new HashSet<>();

		annotationTypes2.add(Annotation1.class);

		AnnotationsTaskClaimer task1 =
				new AnnotationsTaskClaimer(annotationTypes1, fileSet, true);
		AnnotationsTaskClaimer task2 =
				new AnnotationsTaskClaimer(annotationTypes2, fileSet, false);

		TaskRunner.run(task1, task2);

		assertEquals(
				"unexpected number of annotated elements after claiming task",
				0,
				task2.annotatedElements.size());

		printResult(
				"case 3 (annotation set 1 is superset of annotation set 2, claim true)\n", task1, task2);
	}

	/**
	 * Two tasks will be run. The processed set of annotations of the first task
	 * is a sub set of the annotations being processed by the second one. The
	 * first task will claim the annotations but the second task should "see" all
	 * annotated elements (even if a subset of them was processed by a claiming
	 * first task).
	 *
	 * @throws IOException
	 */
	@Test
	public void claimTrueSubAnnotationSet() throws IOException
	{
		Set<Class<? extends Annotation>> annotationTypes1 = new HashSet<>();

		annotationTypes1.add(Annotation1.class);

		Set<Class<? extends Annotation>> annotationTypes2 = new HashSet<>();

		annotationTypes2.add(Annotation1.class);
		annotationTypes2.add(Annotation2.class);

		AnnotationsTaskClaimer task1 =
				new AnnotationsTaskClaimer(annotationTypes1, fileSet, true);
		AnnotationsTaskClaimer task2 =
				new AnnotationsTaskClaimer(annotationTypes2, fileSet, false);

		TaskRunner.run(task1, task2);

		assertEquals(
				"unexpected number of annotated elements after claiming task",
				2,
				task2.annotatedElements.size());

		printResult(
				"case 4 (annotation set 1 is subset of annotation set 2, claim true)\n", task1, task2);
	}

	/**
	 * Two tasks will be run. The processed set of annotations of the first task
	 * is a sub set of the annotations being processed by the second one. The
	 * first task will not claim the annotations so the second task should "see"
	 * all annotated elements.
	 *
	 * @throws IOException
	 */
	@Test
	public void claimFalseSubAnnotationSet() throws IOException
	{
		Set<Class<? extends Annotation>> annotationTypes1 = new HashSet<>();

		annotationTypes1.add(Annotation1.class);

		Set<Class<? extends Annotation>> annotationTypes2 = new HashSet<>();

		annotationTypes2.add(Annotation1.class);
		annotationTypes2.add(Annotation2.class);

		AnnotationsTaskClaimer task1 =
				new AnnotationsTaskClaimer(annotationTypes1, fileSet, false);
		AnnotationsTaskClaimer task2 =
				new AnnotationsTaskClaimer(annotationTypes2, fileSet, false);

		TaskRunner.run(task1, task2);

		assertEquals(
				"unexpected number of annotated elements after non claiming task",
				2,
				task2.annotatedElements.size());

		printResult(
				"case 5 (annotation set 1 is subset of annotation set 2, claim false)\n", task1, task2);
	}

	private void printResult(
			String prefix, AnnotationsTaskClaimer task1, AnnotationsTaskClaimer task2)
  {
		StringBuffer sb = sb(prefix + "  elements visible in task 1\n");

		for (Element e : task1.annotatedElements)
		{
			sb.append("    " + e.getSimpleName() + "\n");
		}
		
		sb.append("  elements visible in task 2\n");

		for (Element e : task2.annotatedElements)
		{
			sb.append("    " + e.getSimpleName() + "\n");
		}
		
		System.out.println(sb);
  }
}